package com.ocm.core.service.impl;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import com.ocm.core.constant.OCMConstant;
import com.ocm.core.domain.AssociationType;
import com.ocm.core.domain.ClassDefinition;
import com.ocm.core.domain.Permission;
import com.ocm.core.domain.PropertyDefinition;
import com.ocm.core.domain.PropertyType;
import com.ocm.core.domain.Security;
import com.ocm.core.domain.Storage;
import com.ocm.core.dto.UserInfoDto;
import com.ocm.core.exception.OCMRuntimeException;
import com.ocm.core.model.ClassInstance;
import com.ocm.core.model.Files;
import com.ocm.core.model.ReferenceValue;
import com.ocm.core.service.AssociationTypeService;
import com.ocm.core.service.JCRService;
import com.ocm.core.service.OCMService;
import com.ocm.core.service.SecurityService;
import com.ocm.core.util.OCMUtil;

@Service("ocmService")
public class OCMServiceImpl implements OCMService {

   private JCRService jcrService;

   private SecurityService securityService;

   @Autowired
   public OCMServiceImpl(@Qualifier("jcrService") JCRService jcrService,
         @Qualifier("securityService") SecurityService securityService) {
      this.jcrService = jcrService;
      this.securityService = securityService;
   }

   public ClassInstance createStorage(Storage storage) {
      String url = storage.getStorageURL();
      Session session = login(url);
      try {
         String storageName = storage.getName().toLowerCase();
         Node node = this.jcrService.addNode(session, null, storageName, null);
         ClassInstance classInstance = this.createFromNode(node);
         return classInstance;
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      } finally {
         logout(session);
      }
   }

   public ClassInstance createClassDefintion(ClassDefinition classDefinition) {
      Storage storage = classDefinition.getStorage();
      String url = storage.getStorageURL();
      Session session = login(url);
      try {
         String classDefinitionName = classDefinition.getName().toLowerCase();
         Node node = null;
         String parentNodeId = classDefinition.getParentClassDefUID() == null ? storage.getStorageUID()
               : classDefinition.getParentClassDefUID().getClassDefUID();
         switch (classDefinition.getClassDefinitionType()) {
         case DOCUMENTCLASSDEF:
            node = this.jcrService.addNode(session, parentNodeId, classDefinitionName, null);
            break;
         case CUSTOMOBJECTDEF:
            node = this.jcrService.addNode(session, parentNodeId, classDefinitionName, null);
            break;
         default:
            break;
         }
         ClassInstance classInstance = this.createFromNode(node);
         return classInstance;
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      } finally {
         logout(session);
      }
   }

   public ClassInstance createClassInstance(ClassInstance classInstance) {
      Storage storage = classInstance.getClassDefinition().getStorage();
      String url = storage.getStorageURL();
      Session session = login(url);
      try {
         ClassDefinition classDefinition = classInstance.getClassDefinition();
         String classDefinitionName = classDefinition.getName().toLowerCase();
         Map<String, Object> propertyValues = convertFromProperyDefs(classInstance.getProperties(), session);
         String parentNodeId = classDefinition.getParentClassDefUID() == null ? storage.getStorageUID()
               : classDefinition.getParentClassDefUID().getClassDefUID();
         String securityObj = null;
         if (classDefinition.getSecurity() != null)
            securityObj = classDefinition.getSecurity().getSecurityInformation(); 

         propertyValues.put(OCMConstant.SYSTEM_SECURITY_PROPERTY, securityObj);
         Node node = null;
         switch (classDefinition.getClassDefinitionType()) {
         case DOCUMENTCLASSINST:
            node = this.jcrService.addNodeHasVersion(session, parentNodeId, classDefinitionName, propertyValues);
            break;
         case CUSTOMOBJECTINST:
            node = this.jcrService.addNode(session, parentNodeId, classDefinitionName, propertyValues);
            break;
         default:
            break;
         }
         classInstance.setNodeUUID(node.getIdentifier());
         classInstance.setRealPath(node.getPath());
         classInstance.setNodeName(node.getName());
         return classInstance;
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      } finally {
         logout(session);
      }
   }

   public Session login(String username, String password, String url) {
      try {
         Session session = jcrService.login(username, password, url);
         return session;
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      }
   }

   public Session login(String url) {
      try {
         UserInfoDto userInfoDto = this.securityService.getCurrentUsername();
         Session session = login(userInfoDto.getUsername(), userInfoDto.getPassword(), url);
         return session;
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      }
   }

   public void logout(Session session) {
      try {
         if (session != null)
            session.logout();
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      }
   }

   public void removeClassDefintion(ClassDefinition classDefinition) {
      Storage storage = classDefinition.getStorage();
      String url = storage.getStorageURL();
      Session session = login(url);
      try {
         this.jcrService.removeNode(session, classDefinition.getClassDefUID());
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      } finally {
         logout(session);
      }
   }

   private ClassInstance createFromNode(Node node) throws RepositoryException {
      ClassInstance classInstance = new ClassInstance();
      classInstance.setNodeUUID(node.getIdentifier());
      classInstance.setNodeName(node.getName());
      classInstance.setRealPath(node.getPath());
      return classInstance;
   }

   public List<ClassInstance> getClassInstanceProperties(Storage storage, List<ClassDefinition> classDefinitions,
         String[] excludeProperties) {
      List<ClassInstance> classInstances = new ArrayList<ClassInstance>();
      String url = storage.getStorageURL();
      Session session = login(url);
      try {
         for (ClassDefinition classDefinition : classDefinitions) {
            if (this.securityService.hasPermission(classDefinition.getSecurity(), Permission.VIEWPROPERTIES)){
               List<PropertyDefinition> properties = classDefinition.getAllProperties();
               properties = OCMUtil.getPropertyDefinitions(excludeProperties, properties);
               String uuid = classDefinition.getClassDefUID();
               ClassInstance classInstance = this.jcrService.getClassInstance(session, uuid, properties, true);
               classInstance.setClassDefinition(classDefinition);
               classInstances.add(classInstance);
            }
         }
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      } finally {
         logout(session);
      }
      return classInstances;
   }

   public Object getPropertyValue(Storage storage, String classInstGUID, PropertyDefinition propertyDefinition,
         List<PropertyDefinition> filterProperties, int maxItems) {
      Object result = null;
      String url = storage.getStorageURL();
      Session session = login(url);
      try {
         Object propertyValue = this.jcrService.getPropertyValue(session, classInstGUID, propertyDefinition);
         if (propertyDefinition.getPropertyType() == PropertyType.OBJECT) {
            String[] objectConfig = PropertyType.parse(propertyDefinition.getObjectConfig());
            long classDef2 = Long.parseLong(objectConfig[2].toString());
            String relation = objectConfig[1];
            AssociationType associatedType = AssociationType.getAssociationType(relation);
            AssociationTypeService associationTypeService = associatedType.getAssociationTypeService(this.jcrService);
            result = associationTypeService.processClassInstance(session, propertyValue, classDef2, filterProperties, maxItems);
         } else {
            result = propertyValue;
         }
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      } finally {
         logout(session);
      }
      return result;
   }

   public boolean checkout(ClassDefinition classDefinition) {
      Storage storage = classDefinition.getStorage();
      String url = storage.getStorageURL();
      Session session = login(url);
      try {
         this.jcrService.checkout(session, classDefinition.getClassDefUID());
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      } finally {
         logout(session);
      }
      return true;
   }

   public boolean cancelCheckout(ClassDefinition classDefinition) {
      Storage storage = classDefinition.getStorage();
      String url = storage.getStorageURL();
      Session session = login(url);
      try {
         this.jcrService.cancelCheckout(session, classDefinition.getClassDefUID());
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      } finally {
         logout(session);
      }
      return true;
   }

   public boolean update(ClassInstance classInstance) {
      ClassDefinition classDefinition = classInstance.getClassDefinition();
      Storage storage = classDefinition.getStorage();
      String url = storage.getStorageURL();
      Session session = login(url);
      try {
         String nodeId = classDefinition.getClassDefUID();
         Map<String, Object> newPropertyValues = convertFromProperyDefs(classInstance.getProperties(), session);
         this.jcrService.updateNode(session, nodeId, newPropertyValues);
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      } finally {
         logout(session);
      }
      return true;
   }

   public boolean checkin(ClassInstance classInstance) {
      ClassDefinition classDefinition = classInstance.getClassDefinition();
      Storage storage = classDefinition.getStorage();
      String url = storage.getStorageURL();
      Session session = login(url);
      try {
         String nodeId = classDefinition.getClassDefUID();
         Map<String, Object> newPropertyValues = convertFromProperyDefs(classInstance.getProperties(), session);
         this.jcrService.checkin(session, nodeId, newPropertyValues);
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      } finally {
         logout(session);
      }
      return true;
   }

   public void removeLastVersion(ClassDefinition classDefinition) {
      Storage storage = classDefinition.getStorage();
      String url = storage.getStorageURL();
      Session session = login(url);
      try {
         String nodeId = classDefinition.getClassDefUID();
         this.jcrService.removeLastVersion(session, nodeId);
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      } finally {
         logout(session);
      }
   }

   public boolean syncSecurity(ClassDefinition classDefinition) {
      Storage storage = classDefinition.getStorage();
      String url = storage.getStorageURL();
      Session session = login(url);
      try {
         Security security = classDefinition.getSecurity();
         String nodeId = classDefinition.getClassDefUID();
         String securityObject = security==null?null:security.getSecurityInformation();
         switch (classDefinition.getClassDefinitionType()) {
         case DOCUMENTCLASSINST:
            this.jcrService.updateSecurityHasVersionable(session, nodeId, securityObject);
            break;
         case CUSTOMOBJECTINST:
            this.jcrService.updateSecurity(session, nodeId, securityObject);
            break;
         }
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      } finally {
         logout(session);
      }
      return true;
   }

   public List<ClassInstance> getHistory(ClassDefinition classDefinition) {
      List<ClassInstance> classInstances = new ArrayList<ClassInstance>();
      Storage storage = classDefinition.getStorage();
      String url = storage.getStorageURL();
      Session session = login(url);
      try {
         String[] names = new String[] { OCMConstant.VERSION_NUMBER, OCMConstant.CREATED_USER,
               OCMConstant.CREATED_DATED, OCMConstant.MODIFYED_DATE, OCMConstant.MODIFYED_USER };
         List<PropertyDefinition> properties = OCMUtil.getPropertyDefinition(names, classDefinition.getAllProperties());
         String nodeId = classDefinition.getClassDefUID();
         List<Map<PropertyDefinition, Object>> propertiesList = this.jcrService
               .historyNode(session, nodeId, properties);
         for (int index = 0; index < propertiesList.size(); index++) {
            Map<PropertyDefinition, Object> propertiesMap = propertiesList.get(index);
            ClassInstance classInstance = new ClassInstance();
            classInstance.setClassDefinition(classDefinition);
            classInstance.setProperties(propertiesMap);
            classInstances.add(classInstance);
         }
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      } finally {
         logout(session);
      }
      return classInstances;
   }

   public Files download(ClassDefinition classDefinition) {
      Storage storage = classDefinition.getStorage();
      String url = storage.getStorageURL();
      Session session = login(url);
      Files files = new Files();
      try {
         String[] names = new String[] { OCMConstant.FILE_NAME, OCMConstant.CONTENT_TYPE, OCMConstant.CONTENT_DATA,
               OCMConstant.CONTENT_LENGTH };
         List<PropertyDefinition> properties = OCMUtil.getPropertyDefinition(names, classDefinition.getAllProperties());
         Map<PropertyDefinition, Object> values = this.jcrService.getValues(session, classDefinition.getClassDefUID(),
               properties);
         for (Iterator<PropertyDefinition> it = values.keySet().iterator(); it.hasNext();) {
            PropertyDefinition propertyDefinition = it.next();
            String propertyName = propertyDefinition.getName();
            Object value = values.get(propertyDefinition);
            if (OCMConstant.CONTENT_DATA.equalsIgnoreCase(propertyName)) {
               files.setInputStream(null);
               if (value != null) {
                  InputStream inputStream = (InputStream) value;
                  files.setInputStream(inputStream);
               }
            } else if (OCMConstant.FILE_NAME.equalsIgnoreCase(propertyName)) {
               files.setFilename(null);
               if (value != null) {
                  files.setFilename("" + value);
               }
            } else if (OCMConstant.CONTENT_TYPE.equalsIgnoreCase(propertyName)) {
               files.setType(null);
               if (value != null) {
                  files.setType(value.toString());
               }
            } else if (OCMConstant.CONTENT_LENGTH.equalsIgnoreCase(propertyName)) {
               files.setLength(0);
               if (value != null) {
                  files.setLength(Integer.parseInt(value.toString()));
               }
            }
         }
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      } finally {
         logout(session);
      }
      return files;
   }

   public Files download(Storage storage, String guid, List<PropertyDefinition> properties) {
      String url = storage.getStorageURL();
      Session session = login(url);
      Files files = new Files();
      try {
         Map<PropertyDefinition, Object> values = this.jcrService.getValues(session, guid, properties);
         for (Iterator<PropertyDefinition> it = values.keySet().iterator(); it.hasNext();) {
            PropertyDefinition propertyDefinition = it.next();
            String propertyName = propertyDefinition.getName();
            Object value = values.get(propertyDefinition);
            if (OCMConstant.CONTENT_DATA.equalsIgnoreCase(propertyName)) {
               files.setInputStream(null);
               if (value != null) {
                  InputStream inputStream = (InputStream) values.get(propertyDefinition);
                  files.setInputStream(inputStream);
               }
            } else if (OCMConstant.FILE_NAME.equalsIgnoreCase(propertyName)) {
               files.setFilename(null);
               if (value != null) {
                  files.setFilename("" + value);
               }
            } else if (OCMConstant.CONTENT_TYPE.equalsIgnoreCase(propertyName)) {
               files.setType(null);
               if (value != null) {
                  files.setType(value.toString());
               }
            } else if (OCMConstant.CONTENT_LENGTH.equalsIgnoreCase(propertyName)) {
               files.setLength(0);
               if (value != null) {
                  files.setLength(Integer.parseInt(value.toString()));
               }
            }
         }
      } catch (Exception e) {
         throw new OCMRuntimeException(e);
      } finally {
         logout(session);
      }
      return files;
   }

   private Map<String, Object> convertFromProperyDefs(Map<PropertyDefinition, Object> propertyDefs, Session session)
         throws Exception {
      Map<String, Object> results = new HashMap<String, Object>();
      for (Iterator<PropertyDefinition> it = propertyDefs.keySet().iterator(); it.hasNext();) {
         PropertyDefinition propertyDefinition = it.next();
         String propertyName = propertyDefinition.getName();
         Object value = propertyDefs.get(propertyDefinition);
         if (value instanceof Value) {
            results.put(propertyName, (Value) value);
         } else if (propertyDefinition.getPropertyType() == PropertyType.OBJECT
               && (value instanceof ReferenceValue && ((ReferenceValue) value).size() > 0)) {
            String relation = PropertyType.parse(propertyDefinition.getObjectConfig())[1];
            ReferenceValue referenceValue = (ReferenceValue) value;
            referenceValue.setAssociationType(AssociationType.getAssociationType(relation));
            results.put(propertyName, referenceValue);
         } else {
            results.put(propertyName, propertyDefinition.getPropertyType().convertValue(value));
         }
      }
      return results;
   }
}
